package com.psychologyCommunity.Community.util;

import org.apache.commons.lang3.CharUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.thymeleaf.util.StringUtils;

import javax.annotation.PostConstruct;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

@Component
public class SensitiveFilter {
    private static final Logger logger= LoggerFactory.getLogger(SensitiveFilter.class);
    //要替换的符号
    private static final String REPLACEMENT="***";

    //根节点
    private TreeNode rootNode=new TreeNode();

    //表示的这是初始化方法 容器实例化SensitiveFilter这个bean之后并且调用构造器之后，这个方法就会被调用
    @PostConstruct
    public  void init(){
        try (
                //.getClassLoader() 表示是从类路径（classes目录下）下去加载资源
                //在try（）中去开启创建某一个对象，编译时会自动加上finally 在finally中关闭
                InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("sensitive-words");
                //从字节流中读字符不太方便，需要把它转变为字符流 ,但最好用缓冲流，读取的效率较高
                BufferedReader Reader=new BufferedReader(new InputStreamReader(resourceAsStream));//缓冲流

        ){
            //每次读取到的词都放到变量里
            String keyWord;
            while ((keyWord = Reader.readLine())!=null){ //一行行读  如果从其中读到了 就把他添加到前缀中去
                //添加到前缀树
                this.addKeyWord(keyWord);
            }
        }catch (IOException e){
            logger.error("加载敏感词文件失败"+ e.getMessage());
        }

    }

    // 这个方法实现 将一个敏感词添加到前缀树 中
    private void addKeyWord(String keyWord){
        TreeNode tempNode=rootNode;//临时结点（相当于一个指针），初始默认为根节点，
        // 然后根据单词拆解某一个字符，不断的指向下一个字符，然后构建结点 挂上去
        for (int i = 0; i < keyWord.length(); i++) {
            char c = keyWord.charAt(i);
            TreeNode subNode=tempNode.getSubNode(c);  //看当前节点是否有子结点   //如果是空的 就给他加一个，有子结点就直接用
            if(subNode==null){
                //如果是空的 就初始化一个子结点
                subNode= new TreeNode();
                tempNode.addSubNode(c,subNode); }
            //让临时指针指向子结点，进入下一轮循环
            tempNode=subNode;

            //设置结束的表示
            if(i==keyWord.length()-1){//如果走到最后了，把最后一个标记为敏感词
                tempNode.setKeyWordEnd(true);
            }
        }

    }
    //实现检索的过程--利用三个指针来检索 看字符串中有没有敏感词  --传入的待过滤的文本，返回的是敏感词被***替换的文本
    public String filter(String text){
        if(StringUtils.isEmpty(text)){ return  null; }
        //指针一
        TreeNode tempNode=rootNode;
        //指针二
        int start=0;
        //指针三
        int position=0;
        //结果
        StringBuilder stringBuilder=new StringBuilder();
        while (position<text.length()){//当活动指针还没到结尾 就一直进行检测判断
            char c = text.charAt(position);
            //跳过特殊符号
            if(isSymbol(c)){
                //若在指向前缀树的临时指针，将此符号计入结果，让字符串的start值针向下走 --但是活动指针不管他是不是符号都往后走
                if(tempNode==rootNode){
                    stringBuilder.append(c);
                    start++;
                }
                //无论符号在开头或中间，指针3都向下一步
                position++;
                //跳过符号后直接进入下一轮
                continue;
            }
            //检查下一级结点
            tempNode= tempNode.getSubNode(c);
            if(tempNode==null){//下一级没有以此时position指向的字符为值的结点
                //说明此时以start开头到目前position的字符串不是 敏感词
                stringBuilder.append(text.charAt(start));//把此时start指针指向的字符加到stringBuilder
                //进入下一个位置
                position= ++start;//等价于 start 后移一位，position移到start的位置
                //重新指向根节点
                tempNode=rootNode;
            }else if(tempNode.isKeyWordEnd()){//不为空 并且已经到前缀树的结尾
                //发现了敏感词，将start移到position的后一位，并且用***替换start开头到目前position的字符串
               stringBuilder.append(REPLACEMENT);
               start= ++position;//将start移到position的后一位，position也是在start后一位
            }else{//最后一种情况就是 position指向的这一个字符在tempNode中有，但还只是疑似，没有到最后标记的状况
                position++;
            }
        }
        //将最后一批字符计入结果--例如图上的最后的ff 不让它漏掉
        stringBuilder.append(text.substring(start));
        return stringBuilder.toString();

    }
    //判断字符是否为特殊符号
    private boolean isSymbol(Character c){
        // CharUtils.isAsciiAlphanumeric（） 表示如果是普通字符（a,b,c\\,1,2,3）返回true，如果是特殊字符就返回false 取个反
      //0x2E80--0x9FFFF表示的东亚的范围（中文、日文、韩文、、）(c<0x2E80 ||c>0x9FFFF) 表示的东亚的范围之外才有可能是特殊符号
       return !CharUtils.isAsciiAlphanumeric(c) && (c<0x2E80 || c>0x9FFFF);
    }

    //前缀树
    private class TreeNode{
        //要描述的是关键词结束的标识 --如果树的结尾就是敏感词
        private boolean isKeyWordEnd=false;

        //要描述 当前节点 的子结点 Character：子结点的字符,TreeNode：子结点的结构
        private Map<Character,TreeNode> subNodes=  new HashMap<>();

        public boolean isKeyWordEnd() {
            return isKeyWordEnd;
        }

        public void setKeyWordEnd(boolean keyWordEnd) {
            isKeyWordEnd = keyWordEnd;
        }

        //添加子结点的方法
        public void addSubNode(Character c,TreeNode node){
            subNodes.put(c,node);
        }

        //获取子结点
        public TreeNode getSubNode(Character c){
            return subNodes.get(c);
        }
    }
}
